<?php

/**
 * Created by PhpStorm.
 * User: yond
 * Date: 2017/2/13
 * Time: 14:20
 */

define('DS', DIRECTORY_SEPARATOR);

class config
{
    private static $cfg = [
        'appdir' => './application', //app目录,绝对路径或相对文件路径
        'url' => [
            'suffix' => 'html'
        ]
    ];

    /**
     * 取得配置
     * @param string $key 支持a.b.c格式取值
     * @return array|mixed|null
     */
    public static function getConfig($key = '')
    {
        if ($key == '') {
            return self::$cfg;
        }
        if (is_string($key)) {
            $key = explode('.', $key);
        }
        if (is_array($key)) {
            $k = static::$cfg;
            for ($i = 0; $i < count($key); $i++) {
                if (!array_key_exists($key[$i], $k)) {
                    return null;
                }
                $k = $k[$key[$i]];
            }
            return $k;
        }
        return null;
    }
}

/**
 * Controller基类
 * Class base
 */
class base
{
    private $tpl;

    public function __construct()
    {
        //实例化模板
        $this->tpl = new tpl();

    }

    /**
     * 变量
     * @param $name
     * @param $value
     */
    public function assign($name, $value)
    {
        $this->tpl->assign($name, $value);
    }

    public function show($tpl)
    {
        $this->tpl->show($tpl);
    }

    public function error($msg = '')
    {
        $this->message($msg, 400);
        exit;
    }

    public function message($msg = '', $code = 200)
    {
        header('Status:' . $code);
        echo $msg;
    }

}

class DocParser
{
    private static $instace = null;

    public static function instance()
    {
        if (self::$instace == null) {
            self::$instace = new self();
        }
        return self::$instace;

    }

    private $params = [];

    public function parse($doc = '')
    {
        $this->params = [];
        if ($doc == '') {
            return $this->params;
        }
        if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment) === false)
            return $this->params;
        $comment = trim($comment [1]);
        if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false)
            return $this->params;
        $this->parseLines($lines [1]);
        return $this->params;
    }

    private function parseLines($lines)
    {
        foreach ($lines as $line) {
            $parsedLine = $this->parseLine($line); // Parse the line

            if ($parsedLine === false && !isset ($this->params ['description'])) {
                if (isset ($desc)) {
                    // Store the first line in the short description
                    $this->params ['description'] = implode(PHP_EOL, $desc);
                }
                $desc = array();
            } elseif ($parsedLine !== false) {
                $desc [] = $parsedLine; // Store the line in the long description
            }
        }
        $desc = implode(' ', $desc);
        if (!empty ($desc))
            $this->params ['long_description'] = $desc;
    }

    private function parseLine($line)
    {
        $line = trim($line);
        if (empty ($line))
            return false;

        if (strpos($line, '@') === 0) {
            if (strpos($line, ' ') > 0) {
                $param = substr($line, 1, strpos($line, ' ') - 1);
                $value = trim(substr($line, strlen($param) + 2));
            } else {
                $param = substr($line, 1);
                $value = '';
            }
            if ($this->setParam($param, $value))
                return false;
        }

        return $line;
    }

    private function setParam($param, $value)
    {
        if ($param == 'param' || $param == 'return')
            $value = $this->formatParamOrReturn($value);
        if ($param == 'class')
            list ($param, $value) = $this->formatClass($value);
        if ($param == 'param' || $param == 'return') {
            $this->params [$param][] = $value;
        } else {
            $this->params [$param] = $value . $this->params [$param];
        }
        return true;
    }

    private function formatClass($value)
    {
        $r = preg_split("[|]", $value);
        if (is_array($r)) {
            $param = $r [0];
            parse_str($r [1], $value);
            foreach ($value as $key => $val) {
                $val = explode(',', $val);
                if (count($val) > 1)
                    $value [$key] = $val;
            }
        } else {
            $param = 'Unknown';
        }
        return array(
            $param,
            $value
        );
    }

    private function formatParamOrReturn($string)
    {
        //@return注释

        $returnCommentArr = explode(' ', $string);
        //将数组中的空值过滤掉，同时将需要展示的值返回
        $returnCommentArr = array_values(array_filter($returnCommentArr));
        if (count($returnCommentArr) < 2) {
            $returnCommentArr[1] = '';
        }
        if (!isset($returnCommentArr[2])) {
            $returnCommentArr[2] = '';    //可选的字段说明
        } else {
            //兼容处理有空格的注释
            $returnCommentArr[2] = implode(' ', array_slice($returnCommentArr, 2));
        }
        return $returnCommentArr;

    }
}

class helper
{
    /**
     * 取得目录下所有文件,包含子目录
     * @param string $dir
     * @return array
     */
    public static function getFielsAndSubDir(string $dir, $filter = [])
    {

        $realDir = realpath($dir);
        if (!$realDir) {
            return [];
        }
        $dir .= substr($realDir, -1) == DS ? '' : DS;
        $info = [];
        foreach (glob($dir . '*') as $v) {
            if (is_dir($v)) {
                $info = array_merge($info, self::getFielsAndSubDir($v));
            } else {
                $info[] = $v;
            }
        }
        return $info;
    }

}

class Refclass extends ReflectionClass
{
    private static $instace = [];
    private static $cureent = null;

    public static function instance($file)
    {
        if (!array_key_exists($file, static::$instace)) {
            self::$instace[$file] = self::$cureent = self::getClassRef($file);
        }
        return self::$cureent;
    }

    private static function getClassRef($file)
    {
        $file = realpath($file);
        $c = file_get_contents($file);
        preg_match('/class([^(\'=\>)]+?)[\{,\r,\n]+/i', $c, $s);
        if (empty($s)) {
            return null;
        }
        $class = $name = trim($s[1]);
        preg_match('/namespace(.+?);/is', $c, $m);
        unset($c);
        if (!empty($m)) {
            $namespace = '\\' . trim($m[1]);
        }
        $class = $namespace . '\\' . $class;
        require_once $file;
        if (!class_exists($class)) {
            return null;
        }
        return new self($class);
    }

    public static function GC()
    {
        unset(static::$instace);
        gc_enable();
        gc_collect_cycles();
    }

    public function getClassBaseinfo()
    {
        return [
            'class' => ltrim(str_replace($this->getNamespaceName(), '', $this->getName()), '\\'),
            'namespace' => $this->getNamespaceName(),
            'realfile' => $this->getFileName(),
            'filename' => rtrim(str_replace(['/', '\\'], [DS, DS], config::getConfig('appdir')), DS) . DS . str_replace(realpath(config::getConfig('appdir')), '', $this->getFileName()),
            'parent' => $this->getParentClass() ? $this->getParentClass()->getName() : '',
        ];
    }

    public function getMethodLists()
    {
        $methods = $this->getMethods(ReflectionMethod::IS_PUBLIC + ReflectionMethod::IS_PROTECTED + ReflectionMethod::IS_PRIVATE);
        foreach ($methods as $m) {
            $lists[] = $this->getMethodInfo($m->getName());
        }
        return $lists;
    }

    public function getMethodInfo($methodName)
    {
        $m = $this->getMethod($methodName);
        $doc = $m->getDocComment();
        $info = [];
        if ($doc) {
            $info = DocParser::instance()->parse($doc);
        }
        $flag = '';
        $flag .= $m->isProtected() ? 'Protected' : '';
        $flag .= $m->isPublic() ? 'Public' : '';
        $flag .= $m->isPrivate() ? 'Private' : '';
        $params = $m->getParameters();
        $args = [];
        foreach ($params as $p) {
            $args[] = [
                'name' => $p->getName(),
                'required' => $p->isDefaultValueAvailable() ? 0 : 1,
                'default' => $p->isDefaultValueAvailable() ? $p->getDefaultValue() : '',
            ];
        }
        return array_merge($info, [
            'method' => $m->getName(),
            'args' => $args,
            'flg' => $flag
        ]);
    }

}

class controller extends base
{

    public function index()
    {
        $files = helper::getFielsAndSubDir(config::getConfig('appdir'));
        $lists = [];
        $docParser = DocParser::instance();
        foreach ($files as $f) {
            $info = Refclass::instance($f);
            if ($info) {
                $doc = $docParser->parse($info->getDocComment());
                $lists[] = array_merge($info->getClassBaseinfo(), $doc);
            }
        }
        $this->assign('lists', $lists);
        $this->assign('title', '类列表');
        $this->show('index');
    }

    public function service()
    {
        $file = app::get('file');
        $file ?: $this->error('参数错误');
        if (!file_exists($file)) $this->error('类文件不存在');
        $Ref = Refclass::instance($file);
        if (!$Ref) $this->error('反射文件失败');
        $info = $Ref->getClassBaseinfo();
        $lists = $Ref->getMethodLists();
        $this->assign('title', $info['class']);
        $this->assign('info', $info);
        $this->assign('lists', $lists);
        $this->show('service');

    }

    public function detail()
    {
        $file = app::get('file');
        $method = app::get('method');
        $file || $method ?: $this->error('参数错误');
        if (!file_exists($file)) $this->error('类文件不存在');
        $Ref = Refclass::instance($file);
        if (!$Ref) $this->error('反射文件失败');
        $classInfo = $Ref->getClassBaseinfo();
        $methodInfo = $Ref->getMethodInfo($method);
        $this->assign('class', $classInfo);
        $this->assign('title', $method);
        $this->assign('method', $methodInfo);
        $this->show('detail');
    }

}

class  app
{
    private static $request;
    public static $config;
    public static $root_url;
    public static $dirname;
    public static $root_path;


    //操作名称
    static $action;
    static $start_time;
    static $end_time;

    private static function rootUrl()
    {
        static::$root_url = 'http://' . $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != 80??'') . $_SERVER['SCRIPT_NAME'];
    }

    /**
     * 系统环境初始化
     * 路由解析
     * 参数过滤
     * 等等
     */
    public static function init()
    {
        define('YO_VERSION', '20170215');
        define('YO_APP_NAME', 'YeSe PHPDOC Helper');
        static::$start_time = microtime();
        self::rootUrl();
        $pathinfo = trim($_SERVER['PATH_INFO'] ? $_SERVER['PATH_INFO'] : (isset($_REQUEST['c']) ? $_REQUEST['c'] : '/index'), '/');
        $patharr = pathinfo($pathinfo);
        static::$root_path = $patharr['dirname'];
        $action = $patharr['filename'];
        static::$request = (array)$_REQUEST;//TODO 请求参数过滤
        if (in_array($action, ['index', 'service', 'list', 'detail'])) {
            static::$action = $action;
        }
    }

    /**
     * 执行入口
     */
    public static function run()
    {
        $controller = new controller();
        $line = __LINE__;
        if (method_exists($controller, static::$action)) {
            call_user_func([$controller, static::$action]);
        } else {
            header('Status:404 Not Found');
            throw new ErrorException("操作不存在", 404, E_ERROR, __FILE__, $line + 2);
        }

        static::$end_time = microtime();

        // echo '脚本时间:' . (string)((float)static::$end_time - (float)static::$start_time);
    }

    public static function url($path = '', $args = [])
    {
        return static::$root_url . '?c=' . $path . '&' . http_build_query($args);
    }

    public static function get($name = '')
    {
        return isset(static::$request[$name]) ? static::$request[$name] : null;
    }


}

class tpl
{
    private $data = [];

    public function assign($name, $value)
    {
        $this->data[$name] = $value;
    }

    public function show($dsp)
    {
        extract($this->data);

//        $cacheNmae = sys_get_temp_dir() . '_' . md5($dsp);
        $cacheNmae = md5($dsp);
        if (!file_exists($cacheNmae)) {//模板缓存
            $content = $this->getTemplate($dsp);
            if (!file_put_contents($cacheNmae, $content)) {
                $cacheNmae = md5($dsp);
                file_put_contents($cacheNmae, $content);
            }
        }
//        ob_start();
        include $cacheNmae;

    }

    private function getTemplate($name)
    {
        $content = file_get_contents(__FILE__);
        preg_match('/<tpl name=\"' . $name . '\">(.+?)<\/tpl>/is', $content, $m);
        unset($content);
        if ($m) {
            return $m[1];
        }
    }
}

error_reporting(0);
app::init();
app::run();

/*
<tpl name="index">
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title><?php echo $title; ?> - 夜色PHP类文档自动生成器</title>
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/semantic.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/table.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/container.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/message.min.css">
    </head>
    <body>
    <br/>
    <div class="ui text container" style="max-width: none !important;">
        <div class="ui floating message">
            <h1 class="ui header">类列表</h1>
            <table class="ui green celled striped table">
                <thead>
                <tr>
                    <th>#</th>
                    <th>类名</th>
                    <th>父类</th>
                    <th>命名空间</th>
                    <th>说明</th>
                    <th>更多说明</th>
                </tr>
                </thead>
                <tbody>
                <?php
                $num = 1;
                $uri = app::url('service');
                $content = '';
                foreach ($lists as $key => $item) {
                    $link = $uri . 'file=' . $item['filename'];
                    $NO = $num++;
                    $content .= "<tr><td>{$NO}</td><td><a href=\"{$link}\" target='_blank'>{$item['class']}</a></td><td>";
                    $content .= $item['parent'] ?: '无';
                    $content .= "</td><td>{$item['namespace']}</td><td>";
                    $content .= array_key_exists('description', $item) ? $item['description'] : '' . "</td><td>";
                    $content .= array_key_exists('long_description', $item) ? $item['long_description'] : '' . "</td></tr>";
                }
                echo $content;
                ?>
                </tbody>
            </table>
            <p>&copy; Powered By <a href="mailto:yondwell@gmail.com" target="_blank"> <?php echo YO_APP_NAME ?></a>
                Ver. <?php echo YO_VERSION; ?>
            <p>
        </div>
    </div>
    </body>
    </html>
</tpl>
*****************************************************************************************************************************************
<tpl name="service">
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>[<?php echo $title ?>]类方法列表-夜色PHP类文档自动生成器</title>
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/semantic.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/table.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/container.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/message.min.css">
    </head>
    <body>
    <br/>
    <div class="ui text container" style="max-width: none !important;">
        <div class="ui floating message">
            <h1 class="ui header">[<?php echo $title ?>]类方法列表</h1>
            <table class="ui green celled striped table">
                <thead>
                <tr>
                    <th>#</th>
                    <th>方法名</th>
                    <th>描述</th>
                    <th>返回类型</th>
                    <th>修饰符</th>
                    <th>作者</th>
                </tr>
                </thead>
                <tbody>
                <?php
                $num = 1;
                $uri = app::url('detail');
                $content = '';
                foreach ($lists as $key => $item) {
                    $link = $uri . 'file=' . $info['filename'] . '&method=' . $item['method'];
                    $NO = $num++;
                    $return = isset($item['return']['type']) ? $item['return']['type'] : 'void';
                    $author = isset($item['author']) ? $item['author'] : '';
                    $content .= "<tr><td>{$NO}</td><td><a href=\"$link\" target='_blank'>{$item['method']}</a></td><td>{$item['description']}</td><td>{$return}</td><td>{$item['flg']}</td><td>{$author}</td></tr>";
                }
                echo $content;
                ?>
                </tbody>
            </table>
            <p>&copy; Powered By <a href="mailto:yondwell@gmail.com" target="_blank"> <?php echo YO_APP_NAME ?></a>
                Ver. <?php echo YO_VERSION; ?>
            <p>
        </div>
    </div>
    </body>
    </html>
</tpl>
***************************************************************************************************************************************
<tpl name="detail">
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title><?php echo $title ?>方法- 夜色PHP类文档自动生成器</title>
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/semantic.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/table.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/container.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/message.min.css">
        <link rel="stylesheet" href="https://staticfile.qnssl.com/semantic-ui/2.1.6/components/label.min.css">
    </head>
    <body>
    <br/>
    <div class="ui text container" style="max-width: none !important;">
        <div class="ui floating message">
            <h2 class='ui header'>方法：<?php echo $title ?></h2><br/>
            <span class='ui teal tag label'><?php echo $method['description'] ?></span>
            <div class="ui raised segment">
                <span class="ui red ribbon label">方法说明</span>
                <div class="ui message">
                    <p><?php echo $method['long_description'] ?></p>
                </div>
            </div>
            <h3>接口参数</h3>
            <table class="ui red celled striped table">
                <thead>
                <tr>
                    <th>参数名字</th>
                    <th>类型</th>
                    <th>是否必须</th>
                    <th>默认值</th>
                    <th>其他</th>
                    <th>说明</th>
                </tr>
                </thead>
                <tbody>
                <?php
                for ($i = 0; $i < count($method['param']); $i++) {
                    $itm = $method['param'][$i];
                    $arg = $method['args'][$i];
                    $type = isset($itm[0]) ? $itm[0] : 'string';
                    $require = isset($arg['required']) && $arg['required'] ? '<font color="red">必须</font>' : '可选';
                    $default = isset($arg['default']) ? $arg['default'] : '';
                    if ($default === NULL) {
                        $default = 'NULL';
                    } else if (is_array($default)) {
                        $default = json_encode($default);
                    } else if (!is_string($default)) {
                        $default = var_export($default, true);
                    }
                    $other = '';
//                    if (isset($rule['min'])) {
//                        $other .= ' 最小：' . $rule['min'];
//                    }
//                    if (isset($rule['max'])) {
//                        $other .= ' 最大：' . $rule['max'];
//                    }
//                    if (isset($rule['range'])) {
//                        $other .= ' 范围：' . implode('/', $rule['range']);
//                    }
                    $desc = isset($itm[2]) ? trim($itm[2]) : '';
                    echo "<tr><td>{$arg['name']}</td><td>$type</td><td>$require</td><td>$default</td><td>$other</td><td>$desc</td></tr>\n";
                }
                ?>
                </tbody>
            </table>
            <h3>返回结果</h3>
            <table class="ui green celled striped table">
                <thead>
                <tr>
                    <th>返回字段</th>
                    <th>类型</th>
                    <th>说明</th>
                </tr>
                </thead>
                <tbody>
                <?php
                foreach ($method['return'] as $item) {
                    $name = $item[1];
                    $type = isset($item[0]) ? $item[0] : 'unknown';
                    $detail = $item[2];
                    echo "<tr><td>$name</td><td>$type</td><td>$detail</td></tr>";
                }
                ?>
                </tbody>
            </table>
            <div class="ui blue message">
                <strong>温馨提示：</strong> 此接口参数列表根据后台代码自动生成.
            </div>
            <p>&copy; Powered By <a href="mailto:yondwell@gmail.com" target="_blank"> <?php echo YO_APP_NAME ?></a>
                Ver. <?php echo YO_VERSION; ?>
            <p>
        </div>
    </div>
    </body>
    </html>
</tpl>
*/
